########################################################################
# Copyright (c) 2008 Burdakov Daniel <kreved@kreved.org>               #
#                                                                      #
# This file is part of PyWoW.                                          #
#                                                                      #
# PyWoW is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or    #
# (at your option) any later version.                                  #
#                                                                      #
# PyWoW is distributed in the hope that it will be useful,             #
# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
# GNU General Public License for more details.                         #
#                                                                      #
# You should have received a copy of the GNU General Public License    #
# along with PyWoW.     If not, see <http://www.gnu.org/licenses/>.    #
########################################################################

from struct import pack, unpack, calcsize
from constants import *
import SRP
import zlib

class NeedOverride(Exception): pass

class packet:
 """template for WoW client-server packets
 """
 def unpack(self, buf):
  """load packet from string buffer
  
  returns 0, if buffer is not enough long,
  count of read bytes otherwise"""
  raise NeedOverride, "Don't know how to unpack %s" % self.__class__
 def pack(self):
  """dump packet to string
  
  returns string, which we can send"""
  raise NeedOverride, "Don't know how to pack %s" % self.__class__

class CAuthChallenge(packet):
 fmt = '<BBH4sBBBH4s4s4sLLB'
 n = calcsize(fmt)
 def __init__(self):
  self.cmd, self.error, self.gamename, self.timezone = 0, 8, 'WoW', 120
 def unpack(self, buf):
  if len(buf) < self.n: return 0
  self.cmd, self.error, self.size, self.gamename, self.version1, \
  self.version2, self.version3, self.build, self.platform, self.os, \
  self.country, self.timezone, self.ip, self.I_len = unpack(self.fmt, buf[:self.n])
  self.gamename = self.gamename[::-1].replace('\x00','')
  self.platform = self.platform[::-1].replace('\x00','')
  self.os = self.os[::-1].replace('\x00','')
  self.country = self.country[::-1].replace('\x00','')
  if len(buf) < self.n + self.I_len: return 0
  self.I = buf[self.n:self.n+self.I_len]
  return self.n + self.I_len
 def pack(self):
  gamename = self.gamename[::-1]
  platform = self.platform[::-1]
  os = self.os[::-1]
  country = self.country[::-1]
  self.I_len = len(self.I)
  self.size = self.n + self.I_len - 4
  p = pack(self.fmt, self.cmd, self.error, self.size, gamename, self.version1, \
  self.version2, self.version3, self.build, platform, os, \
  country, self.timezone, self.ip, self.I_len)
  return p + self.I

class CAuthProof(packet):
 fmt = '<B32s20s20sBB'
 n = calcsize(fmt)
 def __init__(self):
  self.cmd = AUTH_PROOF
  # don't know what is this CRC
  # it is sniffed from original client, but it is different every time.
  self.crc = '\x90\x1d\x0f\x8e\x12N\xc8\xb7|L\x17m\x14\xdb\xf2\x14\x07H!\x14'
  self.number_of_keys = 0
  self.unk = 0
 def unpack(self, buf):
  if len(buf) < self.n: return 0
  self.cmd, self.A, self.cM, self.crc, self.number_of_keys, self.unk = unpack(self.fmt, buf[:self.n])
  self.cM = self.cM[::-1]
  self.A = SRP.string_to_long(self.A[::-1])
  return self.n
 def pack(self):
  A = SRP.long_to_string(self.A)[::-1]
  return pack(self.fmt, self.cmd, A, self.cM[::-1], self.crc, self.number_of_keys, self.unk)

class SAuthChallenge(packet):
 fmt = '<BBB32sBBB32s32s16sB'
 n = calcsize(fmt)
 def pack(self):
  if self.result == AUTH_SUCCESS:
   B = SRP.long_to_string(self.B)[::-1]
   N = SRP.long_to_string(self.N)[::-1]
   s = self.s[::-1]
   unk3 = SRP.random_string(16)
   return pack(self.fmt, AUTH_CHALLENGE, 0, self.result, B, 1, self.g, 32, N, s, unk3, 0)
  else:
   return pack('<BBB', AUTH_CHALLENGE, 0, self.result)
 def unpack(self, buf):
  if len(buf) < 3: return 0
  self.cmd = ord(buf[0])
  self.result = ord(buf[2])
  if self.result == AUTH_SUCCESS:
   if len(buf) < self.n: return 0
   data = unpack(self.fmt, buf[:self.n])
   self.B = SRP.string_to_long(data[3][::-1])
   self.g = data[5]
   self.N = SRP.string_to_long(data[7][::-1])
   self.s = data[8][::-1]
   self.unk3 = SRP.string_to_long(data[9][::-1])
   return self.n
  else:
   return 3

class SAuthProof(packet):
 fmt = '<BB20sLLH'
 n = calcsize(fmt)
 def __init__(self):
  self.cmd = AUTH_PROOF
  self.unk1, self.unk2, self.unk3 = 0x00800000, 0, 0
 def pack(self):
  if self.result == AUTH_SUCCESS:
   return pack(self.fmt, self.cmd, self.result, self.proof, self.unk1, self.unk2, self.unk3)
  else:
   return pack('<BBBB', AUTH_PROOF, self.result, 3, 0)
 def unpack(self, buf):
  if len(buf) < 2: return 0
  self.cmd, self.result = ord(buf[0]), ord(buf[1])
  if self.result == AUTH_SUCCESS:
   if len(buf) < self.n: return 0
   self.cmd, self.result,  self.proof, self.unk1, self.unk2, self.unk3 = unpack(self.fmt, buf[:self.n])
   return self.n
  else: return 4

class CRealmList(packet):
 def pack(self):
  return pack('<BBBBB', REALM_LIST, 0, 0, 0, 0)
 def unpack(self, buf):
  if len(buf) < 5: return 0
  else: return 5

class SRealmList(packet):
 def getCString(self, buf):
  d = buf.find('\x00')
  return buf[:d], buf[d+1:]
 def packRealm(self, realm):
  type, lock, status, name, address, popu, chCount = realm
  buf = pack("<BBB", type, lock, status) + name + '\x00' + address + '\x00'
  buf += pack("<fBBB", popu, chCount, 1, 0x2c)
  return buf
 def unpackRealm(self, buf):
  type, lock, status = unpack("<BBB", buf[:3])
  buf = buf[3:]
  name, buf = self.getCString(buf)
  address, buf = self.getCString(buf)
  popu, chCount = unpack("<fB", buf[:5])
  buf = buf[7:]
  realm = type, lock, status, name, address, popu, chCount
  return realm, buf
 def pack(self):
  realms = map(self.packRealm, self.realms)
  realmspack = pack("<LH", 0, len(self.realms)) + ''.join(realms) + pack("<BB", 16, 0)
  header = pack("<BH", REALM_LIST, len(realmspack))
  return header + realmspack
 def unpack(self, buf):
  if len(buf) < 3: return 0
  _, length = unpack("<BH", buf[:3])
  if len(buf) < length + 3: return 0
  realmspack = buf[3:3+length]
  _, self.realmcount = unpack("<LH", realmspack[:6])
  realmspack = realmspack[6:-2]
  self.realms = []
  for i in range(self.realmcount):
   realm, realmspack = self.unpackRealm(realmspack)
   self.realms.append(realm)
  return length + 3

class CAddonList(packet):
 def unpack(self, buf):
  length = unpack("<L", buf[:4])
  buf = zlib.decompress(buf[4:])
  self.list = []
  while buf:
   n = buf.find('\x00')
   name, buf = buf[:n], buf[n+1:]
   crc, _, _ = unpack("<LLB", buf[:4+4+1])
   buf = buf[4+4+1:]
   self.list.append((name, crc))
 def packAddon(self, (name, crc)):
  return name + '\x00' + pack("<LLB", crc, 0, 0)
 def pack(self):
  p = ''.join(map(self.packAddon, self.list))
  return pack("<L", len(p)) + zlib.compress(p)
